package org.jeecg.common.util;

import com.fasterxml.jackson.core.type.TypeReference;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Http工具类
 * @author qiang.zhou
 */
@Slf4j
public class HttpClientUtil {

    /**
     * 设置编码格式为UTF-8
     */
    private final static String CHARACTER_ENCODING = "UTF-8";

    private final static CloseableHttpClient httpClient = HttpClientUtil.createHttpClient();

    /**
     * 创建HttpClient客户端
     * 这里使用默认的初始话配置
     */
    @SneakyThrows
    private static CloseableHttpClient createHttpClient() {
        HttpClientPoolProperties prop = new HttpClientPoolProperties();
        SSLContext sslcontext;
        if(prop.isIgnoreSSL()) {
            // 信任所有服务器的证书
            sslcontext = new SSLContextBuilder().loadTrustMaterial(null, (x509Certificates, s) -> true).build();
        } else {
            sslcontext = SSLContexts.createDefault();
        }
        SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1.2"},
                null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                // 用来配置支持的协议
                .register("https", factory)
                .build();
        // 使用Httpclient连接池的方式配置(推荐)，同时支持netty，okHttp以及其他http框架
        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        // 最大的连接数量
        connManager.setMaxTotal(prop.getMaxTotalConnect());
        // 默认目标主机的最大连接数
        connManager.setDefaultMaxPerRoute(prop.getMaxPreRoute());

        // 配置基本的请求
        RequestConfig requestConfig = RequestConfig.custom()
                // 设置连接超时
                .setConnectTimeout(prop.getConnectTimeout())
                // 设置读取超时
                .setSocketTimeout(prop.getReadTimeout())
                // 设置从连接池获取连接实例的超时
                .setConnectionRequestTimeout(prop.getConnectionRequestTimeout())
                .build();

        // 设置请求的连接重试策略
        HttpRequestRetryHandler httpRequestRetryHandler = (e, i, httpContext) -> {
            // 重试次数
            if (i >= 3) {
                return false;
            }

            // 如果服务器丢掉连接, 那么就重试
            if (e instanceof NoHttpResponseException) {
                return true;
            }

            // 不重试SSL握手异常
            if (e instanceof SSLHandshakeException) {
                return false;
            }

            // 超时异常不重试
            if (e instanceof InterruptedIOException) {
                return false;
            }

            // 服务器不可达, 不重试
            if (e instanceof UnknownHostException) {
                return false;
            }

            // SSL握手失败, 不重试
            if (e instanceof SSLException) {
                return false;
            }

            HttpClientContext clientContext = HttpClientContext.adapt(httpContext);
            HttpRequest request = clientContext.getRequest();

            // 如果请求是幂等的，就再次尝试
            return !(request instanceof HttpEntityEnclosingRequest);
        };

        CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(connManager)
                .setConnectionManagerShared(true)
                .setRetryHandler(httpRequestRetryHandler)
                // 设置默认的请求标头
                .setDefaultHeaders(defaultHeaders())
                // 设置基本的请求超时配置
                .setDefaultRequestConfig(requestConfig)
                .build();
        return httpClient;
    }

    /**
     * 根据URL下载文件
     *
     * @param url
     * @return
     */
    public static InputStream getFile(String url) {
        try {
            HttpGet get = new HttpGet(url);
            CloseableHttpResponse response = httpClient.execute(get);
            return response.getEntity().getContent();
        } catch (IOException e) {
            log.error("{}请求失败： {}", url, e);
        }
        return null;
    }

    /**
     * 发送表单请求
     *
     * @param url
     * @param params
     * @param headers
     * @return
     */
    public static String postFormData(String url, String reqEncoding, Map<String, Object> params, Map<String, String> headers) throws Exception {
        // 创建请求参数队列
        List<NameValuePair> formParams = new ArrayList<>();
        // 处理Map中的请求参数
        if (params != null) {
            formParams = new ArrayList<>();
            for (Map.Entry<String, Object> param : params.entrySet()) {
                NameValuePair pair = new BasicNameValuePair(param.getKey(), String.valueOf(param.getValue()));
                formParams.add(pair);
            }
        }
        UrlEncodedFormEntity uefEntity = new UrlEncodedFormEntity(formParams, reqEncoding);
        return post(url, uefEntity, headers);
    }

    public static <T> T postFormData(String url, Map<String, Object> params, TypeReference<T> responseType) {
        String json = postFormData(url, params);
        return JacksonUtil.parseObject(json, responseType);
    }

    public static <T> T postFormData(String url, Map<String, Object> params, Class<T> responseType) {
        String json = postFormData(url, params);
        return JacksonUtil.parseObject(json, responseType);
    }

    @SneakyThrows
    public static String postFormData(String url, Map<String, Object> params) {
        return postFormData(url, params, new HashMap<>());
    }

    @SneakyThrows
    public static String postFormData(String url, Map<String, Object> params, Map<String, String> headers) {
        return postFormData(url, CHARACTER_ENCODING, params, headers);
    }

    /**
     * 发送POST请求
     */
    public static String post(String url, HttpEntity httpEntity, Map<String, String> headers) {
        try {
            // 创建httpPost
            HttpPost httpPost = new HttpPost(url);
            httpPost.setEntity(httpEntity);

            // 添加header
            if (ObjectUtils.isNotEmpty(headers)) {
                for (Map.Entry<String, String> header : headers.entrySet()) {
                    httpPost.setHeader(header.getKey(), header.getValue());
                }
            }
            CloseableHttpResponse resultResponse = httpClient.execute(httpPost);
            return responseHandler(httpPost, resultResponse);
        } catch (IOException e) {
            log.error("请求:[{}] 失败； 原因：{}", url, e);
        }
        return null;
    }

    /**
     * 发送POST请求, 以JSON形式
     */
    public static String postJson(String url, Object params, Map<String, String> headers) {
        StringEntity entity = new StringEntity(JacksonUtil.toJsonString(params), ContentType.APPLICATION_JSON);
        entity.setContentType(ContentType.APPLICATION_JSON.toString());
        return post(url, entity, headers);
    }

    public static String postJson(String url, Object params) {
        return postJson(url, params, new HashMap<>(4));
    }

    public static <T> T postJson(String url, Object params, Class<T> clazz) {
        return JacksonUtil.parseObject(postJson(url, params), clazz);
    }

    public static <T> T postJson(String url, Object params, TypeReference<T> typeReference) {
        return JacksonUtil.parseObject(postJson(url, params), typeReference);
    }

    /**
     * 发送GET请求
     * 这个方法暂时不要用
     */
    public static String get(String url) {
        try {
            // 创建httpGet
            HttpGet httpGet = new HttpGet(url);
            // 执行get请求.
            CloseableHttpResponse closeResponse = httpClient.execute(httpGet);
            return responseHandler(httpGet, closeResponse);
        } catch (Exception e) {
            log.error("请求:[{}] 失败； 原因：{}", url, e);
        }
        return null;
    }

    /**
     * 处理返回的response请求, 返回String数据
     */
    private static String responseHandler(HttpRequestBase base, CloseableHttpResponse closeResponse) {
        String result = null;
        HttpEntity httpEntity = closeResponse.getEntity();
        try {
            if (httpEntity != null) {
                result = EntityUtils.toString(httpEntity);
            }
            log.info("请求: {}, 返回的数据为： {}", base.getURI(), result);
        } catch (Exception e) {
            log.error("请求: [{}]地址失败, 原因: {}", base.getURI(), e);
        } finally {
            if (closeResponse != null) {
                try {
                    closeResponse.close();
                } catch (IOException e) {
                    log.warn("{}的响应流关闭失败；原因: {}", base.getURI(), e);
                }
            }
        }
        return result;
    }

    public static void close() {
        try {
            httpClient.close();
        } catch (IOException e) {

        }
    }

    private static List<Header> defaultHeaders() {
        List<Header> headers = new ArrayList<>();
        headers.add(new BasicHeader("User-Agent",
                "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));
        headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
        headers.add(new BasicHeader("Accept-Language", "zh-CN"));
        headers.add(new BasicHeader("Connection", "Keep-Alive"));
        return headers;
    }

    @Data
    public static class HttpClientPoolProperties {
        /**
         * 连接池的最大连接数量
         */
        private int maxTotalConnect = 200;

        /**
         * 同路由的并发数
         */
        private int maxPreRoute = 100;

        /**
         * 连接的超时时间
         */
        private int connectTimeout = 30000;

        /**
         * 读取的超时时间
         */
        private int readTimeout = 30000;
        /**
         * 默认使用的字符编码
         */
        private String charset = "UTF-8";

        /**
         * 默认的重试次数
         */
        private int retryTimes = 2;

        /**
         * 从连接池中获取连接的超时时间; 单位: ms, 这里的时间不能设的太长
         */
        private int connectionRequestTimeout = 100;

        /**
         * 针对不同主机, 设置不同的长连接保持时间
         */
        private Map<String, Integer> keepAliveTargetHost;

        /**
         * 针对不同地址, 设置不同长连接的保持时间
         */
        private int keepAliveTime = 60;

        /**
         * 是否开启守护线程, 默认不开启;
         */
//    private Boolean startDeamonThread = false;

        /**
         * 不活跃的线程回收的时长, 默认30秒
         */
        private int inactionThreadTime = 30;

        private boolean ignoreSSL = true; // 是否忽略证书检查

    }

}
